#include <common.h>
#include <isr.h>
#include <monitor.h>
#include <task.h>
#include <drivers/keyboard.h>
#include <Starflub/Starflub.h>

#define	RAW1_LEFT_CTRL		0x1D
#define	RAW1_RIGHT_CTRL		0x1D	/* same as left */
#define	RAW1_LEFT_SHIFT		0x2A
#define	RAW1_RIGHT_SHIFT	0x36
#define	RAW1_LEFT_ALT		0x38
#define	RAW1_RIGHT_ALT		0x38	/* same as left */
#define	RAW1_CAPS_LOCK		0x3A
#define	RAW1_F1			0x3B
#define	RAW1_F2			0x3C
#define	RAW1_F3			0x3D
#define	RAW1_F4			0x3E
#define	RAW1_F5			0x3F
#define	RAW1_F6			0x40
#define	RAW1_F7			0x41
#define	RAW1_F8			0x42
#define	RAW1_F9			0x43
#define	RAW1_F10		0x44
#define	RAW1_NUM_LOCK		0x45
#define	RAW1_SCROLL_LOCK	0x46
#define	RAW1_F11		0x57
#define	RAW1_F12		0x58
#define	KBD_META_ALT	0x0200	/* Alt is pressed */
#define	KBD_META_CTRL	0x0400	/* Ctrl is pressed */
#define	KBD_META_SHIFT	0x0800	/* Shift is pressed */
#define	KBD_META_ANY	(KBD_META_ALT | KBD_META_CTRL | KBD_META_SHIFT)
#define	KBD_META_CAPS	0x1000	/* CapsLock is on */
#define	KBD_META_NUM	0x2000	/* NumLock is on */
#define	KBD_META_SCRL	0x4000	/* ScrollLock is on */

/* "ASCII" values for non-ASCII keys. All of these are user-defined.

Hrrm. Unicode defines code pages for pseudographics (e.g. box-drawing
characters). I wonder it defines anything for keys like these?

function keys: */
#define	KEY_F1		0x80
#define	KEY_F2		(KEY_F1 + 1)
#define	KEY_F3		(KEY_F2 + 1)
#define	KEY_F4		(KEY_F3 + 1)
#define	KEY_F5		(KEY_F4 + 1)
#define	KEY_F6		(KEY_F5 + 1)
#define	KEY_F7		(KEY_F6 + 1)
#define	KEY_F8		(KEY_F7 + 1)
#define	KEY_F9		(KEY_F8 + 1)
#define	KEY_F10		(KEY_F9 + 1)
#define	KEY_F11		(KEY_F10 + 1)
#define	KEY_F12		(KEY_F11 + 1)
/* cursor keys */
#define	KEY_INS		0x90
#define	KEY_DEL		(KEY_INS + 1)
#define	KEY_HOME	(KEY_DEL + 1)
#define	KEY_END		(KEY_HOME + 1)
#define	KEY_PGUP	(KEY_END + 1)
#define	KEY_PGDN	(KEY_PGUP + 1)
#define	KEY_LFT		(KEY_PGDN + 1)
#define	KEY_UP		(KEY_LFT + 1)
#define	KEY_DN		(KEY_UP + 1)
#define	KEY_RT		(KEY_DN + 1)
/* print screen/sys rq and pause/break */
#define	KEY_PRNT	(KEY_RT + 1)
#define	KEY_PAUSE	(KEY_PRNT + 1)
/* these return a value but they could also act as additional bucky keys */
#define	KEY_LWIN	(KEY_PAUSE + 1)
#define	KEY_RWIN	(KEY_LWIN + 1)
#define	KEY_MENU	(KEY_RWIN + 1)
static const unsigned char kbdus[] =
{
/* 00 */0,	27,	'1',	'2',	'3',	'4',	'5',	'6',
/* 08 */'7',	'8',	'9',	'0',	'-',	'=',	'\b',	'\t',
/* 10 */'q',	'w',	'e',	'r',	't',	'y',	'u',	'i',
		/* 1Dh is left Ctrl */
/* 18 */'o',	'p',	'[',	']',	'\n',	0,	'a',	's',
/* 20 */'d',	'f',	'g',	'h',	'j',	'k',	'l',	';',
		/* 2Ah is left Shift */
/* 28 */'\'',	'`',	0,	'\\',	'z',	'x',	'c',	'v',
		/* 36h is right Shift */
/* 30 */'b',	'n',	'm',	',',	'.',	'/',	0,	0,
		/* 38h is left Alt, 3Ah is Caps Lock */
/* 38 */0,	' ',	0,	KEY_F1,	KEY_F2,	KEY_F3,	KEY_F4,	KEY_F5,
		/* 45h is Num Lock, 46h is Scroll Lock */
/* 40 */KEY_F6,	KEY_F7,	KEY_F8,	KEY_F9,	KEY_F10,0,	0,	KEY_HOME,
/* 48 */KEY_UP,	KEY_PGUP,'-',	KEY_LFT,'5',	KEY_RT,	'+',	KEY_END,
/* 50 */KEY_DN,	KEY_PGDN,KEY_INS,KEY_DEL,0,	0,	0,	KEY_F11,
/* 58 */KEY_F12
};
static const unsigned char kbduss[] =
{
/* 00? */0,	27,	'!',	'@',	'#',	'$',	'%',	'^',
/* 08 */'&',	'*',	'(',	')',	'_',	'+',	'\b',	'\t',
/* 10 */'Q',	'W',	'E',	'R',	'T',	'Y',	'U',	'I',
		/* 1Dh is left Ctrl */
/* 18 */'O',	'P',	'{',	'}',	'\n',	0,	'A',	'S',
/* 20 */'D',	'F',	'G',	'H',	'J',	'K',	'L',	':',
		/* 2Ah is left Shift */
/* 28 */'"',	'~',	0,	'|',	'Z',	'X',	'C',	'V',
		/* 36h is right Shift */
/* 30 */'B',	'N',	'M',	'<',	'>',	'?',	0,	0,
		/* 38h is left Alt, 3Ah is Caps Lock */
/* 38 */0,	' ',	0,	KEY_F1,	KEY_F2,	KEY_F3,	KEY_F4,	KEY_F5,
		/* 45h is Num Lock, 46h is Scroll Lock */
/* 40 */KEY_F6,	KEY_F7,	KEY_F8,	KEY_F9,	KEY_F10,0,	0,	KEY_HOME,
/* 48 */KEY_UP,	KEY_PGUP,'-',	KEY_LFT,'5',	KEY_RT,	'+',	KEY_END,
/* 50 */KEY_DN,	KEY_PGDN,KEY_INS,KEY_DEL,0,	0,	0,	KEY_F11,
/* 58 */KEY_F12
};
/* printable char */

int int01_flag=0;
extern task_t *focused_task, *current_task;
int get_kb_ok=1;
int is_shift=0;
int otherFlag = 0;

kbcallback kbcallb;
char uinput[256];
int uindex=0;

int enabled = 0;
int awaitEnter = 0;

void kb_enable() { enabled = 1; }
void kb_disable() { enabled = 0; }

void kb_read(char *prompt, kbcallback callb) {
	monitor_write(prompt);
	monitor_set_freeze_point();
	kbcallb = callb;
	awaitEnter=0;
	kb_enable();
}

void kb_waitForEnter(kbcallback callb) {
	awaitEnter = 1;
	kbcallb=callb;
	kb_enable();
}

char getch_keys_gets()
{
   //the ascii char we want to return
	char ch;
	// Make a while loop waiting for process to finish getting input 
          //this holds the scan code
	char scan_code;

          //label that might be used later
wait_press:
	  while(otherFlag != 999) {
	  	  otherFlag = 0;
		  hlt();
	  }
     
     otherFlag = 0;
	  int01_flag = 0;
         
          //store the scan code
	  scan_code=inb(0x60);

          //if the top bit is not set then return the character
	  if((scan_code&0x80)==0){
		if (!is_shift) ch = kbdus[ scan_code ];
		else
			ch=kbduss[ scan_code];
		  return ch;
	  }else{
                  //otherwise go back to waiting.
		  //goto wait_press;
		  return 'x';
	  } 
} 
char getch_keys()
{
          //the ascii char we want to return
          char ch;

	  while((!get_kb_ok) || (current_task != focused_task))
	  {
		  hlt();
	  }
          //this holds the scan code
          char scan_code;

          //label that might be used later
          wait_press:
	  get_kb_ok=0;	  
	  int01_flag =0;
          while(int01_flag == 0)
                     hlt();
         
          int01_flag = 0;
         
          //store the scan code
          scan_code=inb(0x60);

          //if the top bit is not set then return the character
	  
          if((scan_code&0x80)==0){
                  if (!is_shift) ch = kbdus[ scan_code ];
		else
			ch=kbduss[ scan_code];
		  get_kb_ok=1;
                  return ch;
          }else{
                  //otherwise go back to waiting.
                  goto wait_press;
          } 
} 

char *gets(char *buf)
{
	kb_install();
	task_t *ta=current_task;
	while((!get_kb_ok) || (ta != focused_task))
	{
		hlt();
	}
	get_kb_ok=0;
	int i;
	int c;
	buf[0] = '\0';
	i = 0;
	while (1) {
		c = getch_keys_gets();
		if (c == -'1')
			break;
		if (c == '\r' || c == '\n') {
			break;
		}
		if (c == '\b' || c == 0x7f) {
			if (i > 0) {
				i--;
				buf[i] = '\0';
			}
			continue;
		}
		buf[i++] = c;
	}
	buf[i] = '\0';
	get_kb_ok=1;
	kb_uninstall();
	return buf;
}

static void write_kbd(unsigned adr, unsigned data)
{
	unsigned long timeout;
	unsigned stat;

	/* Linux code didn't have a timeout here... */
	for(timeout = 500000L; timeout != 0; timeout--)
	{
		stat = inb(0x64);
		/* loop until 8042 input buffer empty */
		if((stat & 0x02) == 0)
			break;
	}
	if(timeout != 0)
		outb(adr, data);
}

unsigned convert(unsigned key)
{

	static unsigned short kbd_status, saw_break_code;

	unsigned char temp;

	/* check for break key (i.e. a key is released) */
	if(key >= 0x80)
	{
		saw_break_code = 1;
		key &= 0x7F;
	}
	/* the only break codes we're interested in are Shift, Ctrl, Alt */
	if(saw_break_code)
	{
		if(key == RAW1_LEFT_ALT || key == RAW1_RIGHT_ALT)
			kbd_status &= ~KBD_META_ALT;
		else if(key == RAW1_LEFT_CTRL || key == RAW1_RIGHT_CTRL)
			kbd_status &= ~KBD_META_CTRL;
		else if(key == RAW1_LEFT_SHIFT || key == RAW1_RIGHT_SHIFT)
			kbd_status &= ~KBD_META_SHIFT;
		saw_break_code = 0;
		return 0;
	}
	/* it's a make key: check the "meta" keys, as above */
	if(key == RAW1_LEFT_ALT || key == RAW1_RIGHT_ALT)
	{
		kbd_status |= KBD_META_ALT;
		return 0;
	}
	if(key == RAW1_LEFT_CTRL || key == RAW1_RIGHT_CTRL)
	{
		kbd_status |= KBD_META_CTRL;
		return 0;
	}
	if(key == RAW1_LEFT_SHIFT || key == RAW1_RIGHT_SHIFT)
	{
		kbd_status |= KBD_META_SHIFT;
		return 0;
	}
/* Scroll Lock, Num Lock, and Caps Lock set the LEDs. These keys
	have on-off (toggle or XOR) action, instead of momentary action */
	if(key == RAW1_SCROLL_LOCK)
	{
		kbd_status ^= KBD_META_SCRL;
		goto LEDS;
	}
	if(key == RAW1_NUM_LOCK)
	{
		kbd_status ^= KBD_META_NUM;
		goto LEDS;
	}
	if(key == RAW1_CAPS_LOCK)
	{
		kbd_status ^= KBD_META_CAPS;
		LEDS:		
		write_kbd(0x60, 0xED);	/* "set LEDs" command */
		temp = 0;
		if(kbd_status & KBD_META_SCRL)
			temp |= 1;
		if(kbd_status & KBD_META_NUM)
			temp |= 2;
		if(kbd_status & KBD_META_CAPS)
			temp |= 4;
		write_kbd(0x60, temp);	/* bottom 3 bits set LEDs */
		return 0;
	}
	/* ignore invalid scan codes */
	//if(key >= sizeof(set1_map) / sizeof(set1_map[0]))
	//	return 0;
	/* convert raw scancode in key to unshifted ASCII in temp */
	temp = kbdus[key];
	/* defective keyboard? non-US keyboard? more than 104 keys? */
	if(temp == 0)
		return temp;
	/* handle the three-finger salute */
	if((kbd_status & KBD_META_CTRL) && (kbd_status & KBD_META_ALT) && (temp == KEY_DEL))
	{
		//kernel_reset();
		SFKernelPanic("Three-finger salute!", "User");
	}
	if(kbd_status & KBD_META_SHIFT)
		is_shift=1;
	else
		is_shift=0;
	/* I really don't know what to do yet with Alt, Ctrl, etc. -- punt */
	return temp;
}

/* Handles the keyboard interrupt */
void kb_main()
{
    unsigned char scancode;
    int01_flag=1;
    /* Read from the keyboard's data buffer */
    scancode = inb(0x60);

    /* If the top bit of the byte we read from the keyboard is
    *  set, that means that a key has just been released */
	convert(scancode);
    if (scancode & 0x80)
    {
        
    }
    else
    {
    		if(kbdus[scancode]=='\n') {
    			if(kbcallb != 0) {
    				if(awaitEnter==1)
    					kbcallb("");
    				else {
		 				monitor_write("\r\n");
		 				kbcallb(uinput);
    				}
    				kb_disable();
    			}
    		}
    			
         if(!is_shift) {
         	if(awaitEnter==0)
	         	monitor_put(kbdus[scancode]);
         	uinput[uindex++] = kbdus[scancode];
         }
			else {
				if(awaitEnter==0)
					monitor_put(kbduss[scancode]);
				uinput[uindex++] = kbdus[scancode];
			}
			if(kbdus[scancode] == '\b') {
				if(awaitEnter==0)
					monitor_backspace();
				uinput[uindex--]='\0';
			}
    }
}
extern int mode_is_vga;
extern u8int cursor_x;
extern u8int cursor_y;
u32int term_handle(int type, u32int d2, u32int d3)
{
	if(type == IO_READ)
	{
		return 'x';//getch_keys();
	}
	else if(type == IO_WRITE)
	{
		puts(d2);
	}
}

long kb_install() {
	register_interrupt_handler(IRQ1, &kb_main);
//	install_io_req(&term_handle);
}

long kb_uninstall() {
	unregister_interrupt_handler(IRQ1);
}
